﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Collections;
using System.Xml.Schema;
using System.Xml;
using NHibernate.UserTypes;
using System.Data.Common;
using NHibernate.SqlTypes;
using System.Reflection;
using System.Runtime.Serialization;

namespace EasyStep.Data
{
    /// <summary>
    /// entity fields, which have to be stored into xml filed
    /// have to be marked with this attribute
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public class XmlDataFieldAttribute : Attribute
    {

    }

    /// <summary>
    /// entity, with XmlFileds have to be inherits from this class
    /// </summary>
    [DataContract(Namespace = "")]
    public class DataTypeWithXml
    {
        
        private DateTime? _Created;
        [XmlDataField]
        [DataMember]
        public DateTime Created {
            get {
                if (!_Created.HasValue) _Created = DateTime.Now;
                return _Created.Value;
            }
            set { _Created = value; }
        }
        
        private DateTime? _Updated;
        [XmlDataField]
        [DataMember]
        public DateTime Updated
        {
            get
            {
                if (!_Updated.HasValue) _Updated = DateTime.Now;
                return _Updated.Value;
            }
            set { _Updated = value; }
        }

        private Type type { get; set; }

        public DataTypeWithXml()
        {
            type = this.GetType();
        }
        private XmlDictionary xItems;
        private XmlDictionary XItems
        {
            get
            {
                if (xItems == null) xItems = new XmlDictionary();
                return xItems;
            }
            set { xItems = value; }
        }
        private XmlDictionary XmlFields
        {
            get
            {
                FromProperties();
                return XItems;
            }
            set
            {
                XItems = value;
                ToProperties();
            }
        }

        private void ToProperties()
        {

            var props =type.GetProperties();
            if (XItems == null) return; //we haven't any property 
            foreach (var item in props)
            {
                if (item.GetCustomAttributes(typeof(XmlDataFieldAttribute), false).Length > 0)
                {
                    var xdi = XItems[item.Name];
                    if (xdi != null) item.SetValue(this, xdi, null);//copy value from xml to property
                }
            }
        }
        private void FromProperties()
        {
            var props = type.GetProperties();
            foreach (var item in props)
                if (item.GetCustomAttributes(typeof(XmlDataFieldAttribute), false).Length > 0)
                {
                    object val = item.GetValue(this, null);
                    XItems[item.Name] = val ;
                };
        }

        public virtual XmlSchema GetSchema()
        {
            return null;
        }
    }

    /// <summary>
    /// custom nhibernate field, needs to store Xml fields
    /// entity table have to has xml filed which have to be mapped to this type
    /// 
    /// </summary>
    public class XmlFields : IUserType
    {
        public object Assemble(object cached, object owner)
        {
            return this.DeepCopy(cached);
        }
        public object Disassemble(object value)
        {
            return this.DeepCopy(value);
        }

        public object DeepCopy(object value)
        {
            XmlDictionary cvalue = value as XmlDictionary;
            if (cvalue == null) return null;
            XmlDictionary copy = new XmlDictionary();
            cvalue.Copy(copy);
            return copy;
        }

        public new bool Equals(object x, object y)
        {
            if (x == null || y == null) return false;
            XmlDictionary xd = x as XmlDictionary;
            XmlDictionary yd = y as XmlDictionary;
            if (xd == null || yd == null) return false;
            if (xd.Count != yd.Count) return false;
            foreach (var item in xd)
                if (!yd.ContainsKey(item.Key) || yd[item.Key] != item.Value)
                {
                    return false;
                }
            foreach (var item in yd)
                if (!xd.ContainsKey(item.Key) || xd[item.Key] != item.Value)
                    return false;

            return true;
        }

        public int GetHashCode(object x)
        {
            return x.GetHashCode();
        }


        public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
        {
            var parameter = (DbParameter)cmd.Parameters[index];

            if (value == null)
            {
                parameter.Value = DBNull.Value;
                return;
            }
            parameter.Value = (value as XmlDictionary).Serialize();
        }

        public object Replace(object original, object target, object owner)
        {
            return original;
        }

        public Type ReturnedType
        {
            get { return typeof(XmlDictionary); }
        }

        public SqlType[] SqlTypes
        {
            get { return new SqlType[] { new NHibernate.SqlTypes.XmlSqlType() }; }
        }


        public bool IsMutable
        {
            get { return true; }
        }

        public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
        {
            if (names.Length != 1)
                throw new InvalidOperationException("names array has more than one element. can't handle this!");

            var document = new XmlDictionary();
            var val = rs[names[0]] as string;
            if (val != null)
            {
                document = val.Deserialize<XmlDictionary>();
                return document;
            }
            return null;
        }
    }



    #region custom xml fields will be serialized to from by this
    public class XmlDictionary : IEnumerable<KeyValuePair<string, object>>, ICollection<KeyValuePair<string, object>>, IXmlSerializable
    {
        private Dictionary<string, object> items;
        public XmlDictionary()
        {
            items = new Dictionary<string, object>();
        }
        public XmlSchema GetSchema()
        {
            return null;
        }
        public void ReadXml(XmlReader reader)
        {
            if (!reader.IsEmptyElement)
            {
                if (!reader.Read()) return;
                while (reader.NodeType != XmlNodeType.EndElement)
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        string name = reader.Name;
                        var tname = reader.GetAttribute("t");
                        string value = reader.ReadInnerXml();
                        if (string.IsNullOrEmpty(value))
                            this.items.Add(name,null);
                        else
                        {
                            this.items.Add(name, value.Deserialize(tname));
                        }
                    }
            }
            reader.Read();
        }
        public void WriteXml(XmlWriter writer)
        {
            foreach (var item in items)
            {
                writer.WriteStartElement(item.Key);
                var tname = item.Value.GetType().FullName;
                writer.WriteAttributeString("t",tname );
                if (item.Value != null) writer.WriteRaw(item.Value.Serialize(tname));
                writer.WriteEndElement();
            }
        }

        IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
        {
            return items.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return items.Values.GetEnumerator();
        }

        public void Copy(XmlDictionary destination)
        {
            foreach (var item in items)
                if (destination.items.ContainsKey(item.Key)) destination.items[item.Key] = item.Value;
                else destination.items.Add(item.Key, item.Value);
        }
        public object this[string key]
        {
            get { return items.ContainsKey(key) ? items[key] : null; }
            set
            {
                if (items.ContainsKey(key)) items[key] = value;
                else items.Add(key, value);
            }
        }
        public void Clear()
        {
            items.Clear();
        }
        public int Count
        {
            get { return items.Count; }
        }

        public bool ContainsKey(string key)
        {
            return items.ContainsKey(key);
        }


        public void Add(KeyValuePair<string,object> item)
        {
            this[item.Key] = item;
        }

        public bool Contains(KeyValuePair<string, object> item)
        {
            return items.ContainsKey(item.Key);
        }

        public void CopyTo(object[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }
        public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)        
        {
            throw new NotImplementedException();
        }
        public bool Remove(KeyValuePair<string, object> item)
        {
           return items.Remove(item.Key);
        }
        public bool IsReadOnly
        {
            get { return false; }
        }

        

        public bool IsSynchronized
        {
            get { return true; }
        }

        public object SyncRoot
        {
            get { return this; }
        }


    }



    #endregion
}
